/*
 * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.huawei.cloudphone.virtualdevice.common;

import static com.huawei.cloudphone.api.CloudPhoneParas.DEV_TYPE_CAMERA;
import static com.huawei.cloudphone.api.CloudPhoneParas.DEV_TYPE_LOCATION;
import static com.huawei.cloudphone.api.CloudPhoneParas.DEV_TYPE_MICROPHONE;
import static com.huawei.cloudphone.api.CloudPhoneParas.DEV_TYPE_SENSOR;

import android.content.Context;
import android.hardware.SensorManager;
import android.util.Log;

import com.huawei.cloudphone.api.CloudPhonePermissionRequestListener;
import com.huawei.cloudphone.common.CASLog;
import com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager;
import com.huawei.cloudphone.virtualdevice.location.VirtualLocationManager;
import com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophoneManager;
import com.huawei.cloudphone.virtualdevice.sensor.VirtualSensorManager;

import java.util.HashMap;
import java.util.Map;

public class VirtualDeviceProtocol {
    private static final String TAG = "VirtualDeviceProtocol";
    public static int MSG_HEADER_LEN = 16;
    public static int WAITING_INTERVAL = 200;
    private IVirtualDeviceIO mVirtualDeviceIO;
    private boolean mIsTaskRun;
    private PacketParseThread mPktProcessThread;
    static Object mSendMsgLock = new Object();
    private Map<Short, VirtualDeviceManager> virtualDeviceManagers;
    private Context mContext;

    public VirtualDeviceProtocol(IVirtualDeviceIO virtualDeviceIO , Context context) {
        mVirtualDeviceIO = virtualDeviceIO;
        mContext = context;
    }

    public void initVirtualDeviceManagers(CloudPhonePermissionRequestListener listener) {
        virtualDeviceManagers = new HashMap<Short, VirtualDeviceManager>();
        virtualDeviceManagers.put(DEV_TYPE_CAMERA, new VirtualCameraManager(this, mContext));
        virtualDeviceManagers.put(DEV_TYPE_MICROPHONE, new VirtualMicrophoneManager(this, mContext));
        virtualDeviceManagers.put(DEV_TYPE_SENSOR, new VirtualSensorManager(this,
                (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE)));
        virtualDeviceManagers.put(DEV_TYPE_LOCATION, new VirtualLocationManager(this, mContext));

        // 设置权限监听
        if (listener != null) {
            ((VirtualCameraManager)virtualDeviceManagers.get(DEV_TYPE_CAMERA)).setPermissionListener(listener);
            ((VirtualMicrophoneManager)virtualDeviceManagers.get(DEV_TYPE_MICROPHONE)).setPermissionListener(listener);
            ((VirtualLocationManager)virtualDeviceManagers.get(DEV_TYPE_LOCATION)).setPermissionListener(listener);
        }
    }

    public VirtualDeviceManager getVirtualDeviceManager(short devType) {
        if (virtualDeviceManagers == null) {
            return null;
        }
        return virtualDeviceManagers.get(devType);
    }

    public void processPermissionResult(short requestCode, int grantResult) {
        if (grantResult != 0) {
            return;
        }
        VirtualDeviceManager deviceManager = virtualDeviceManagers.get(requestCode);
        if (deviceManager == null) {
            return;
        }
        deviceManager.init();
    }

    public void processMsg(MsgHeader header, byte[] body) {
        short devType = header.mDeviceType;
        if (devType == 0) devType = DEV_TYPE_SENSOR;
        VirtualDeviceManager virtualDeviceManager = virtualDeviceManagers.get(devType);
        if (virtualDeviceManager == null) {
            Log.e(TAG, "processMsg: Error msg type :" + header.mDeviceType);
            return;
        }
        virtualDeviceManager.processMsg(header, body);
    }

    public void startProcess() {
        mIsTaskRun = true;
        mPktProcessThread = new PacketParseThread();
        mPktProcessThread.start();
        for (Map.Entry<Short, VirtualDeviceManager> entry : virtualDeviceManagers.entrySet()) {
            entry.getValue().start();
        }
    }

    public void stopProcess() {
        mIsTaskRun = false;
        mPktProcessThread.interrupt();
        try {
            mPktProcessThread.join();
        } catch (InterruptedException e) {
            CASLog.e(TAG, "stop PacketParseThread failed. " + e.getMessage());
        }
        for (Map.Entry<Short, VirtualDeviceManager> entry : virtualDeviceManagers.entrySet()) {
            entry.getValue().stop();
        }
    }

    public void sendMsg(MsgHeader header, byte[] body, int deviceType) {
        synchronized (mSendMsgLock) {
            int length = header.getData().length;
            if(body != null) {
                length += body.length;
            }
            byte[] data = new byte[length];
            System.arraycopy(header.getData(), 0, data, 0, header.getData().length);
            if(body != null) {
                System.arraycopy(body, 0, data, header.getData().length, body.length);
            }
            writeN(data, 0, length, deviceType);
        }
    }

    private int readN(byte[] data, int offset, int len) {
        int readLen = 0;
        while(len > 0) {
            if(!mIsTaskRun) {
                break;
            }
            int retLen = mVirtualDeviceIO.readN(data, offset, len);
            if(retLen > 0) {
                len -= retLen;
                offset += retLen;
                readLen += retLen;
                continue;
            }
            try {
                Thread.sleep(WAITING_INTERVAL);
            } catch (InterruptedException e) {
                Log.e(TAG, "readN: sleep is interrupt");
            }
        }
        return readLen;
    }

    private int writeN(byte[] data, int offset, int len, int deviceType) {
        int writeLen = 0;
        while(len > 0) {
            if(!mIsTaskRun) {
                break;
            }
            int retLen = mVirtualDeviceIO.writeN(data, offset, len, deviceType);
            if(retLen > 0) {
                len -= retLen;
                offset += retLen;
                writeLen += retLen;
                continue;
            }
            try {
                Thread.sleep(WAITING_INTERVAL);
            } catch (InterruptedException e) {
                Log.e(TAG, "readN: sleep is interrupt", e);
            }
        }
        return writeLen;
    }

    class PacketParseThread extends Thread {
        byte[] header = new byte[MSG_HEADER_LEN];
        @Override
        public void run(){
            while (mIsTaskRun) {
                if (readN(header, 0, MSG_HEADER_LEN) != MSG_HEADER_LEN) {
                    Log.e(TAG, "Read msg header error");
                    continue;
                }
                MsgHeader msgHeader = new MsgHeader(header);
                int bodyLen = msgHeader.mPayloadLength;
                if (bodyLen < 0) {
                    Log.e(TAG, "Read msg header error, bodyLen = " + bodyLen);
                    continue;
                }
                byte[] body = new byte[bodyLen];
                if (readN(body, 0, bodyLen) != bodyLen) {
                    Log.e(TAG, "Read msg header error");
                    continue;
                }
                processMsg(msgHeader, body);
            }
        }
    }

    public static class MsgHeader {
        public short mVersion;
        public short mOptType;
        public short mDeviceType;
        public short mDeviceId;
        public short mNextProtocol;
        public short mHopLimit;
        public int mPayloadLength;
        private byte[] mData = null;

        public MsgHeader(byte[] data) {
            initParam(data);
            mData = new byte[data.length];
            System.arraycopy(data, 0, mData, 0, data.length);
        }

        public MsgHeader(short optType, short devType, short devId, int msgLen) {
            mData = new byte[MSG_HEADER_LEN];
            mData[0] = 0;
            mData[1] = 0x01;

            mData[2] = (byte) (optType >> 8);
            mData[3] = (byte) (optType & 0x00FF);

            mData[4] = (byte) (devType >> 8);
            mData[5] = (byte) (devType & 0xFF);

            mData[6] = (byte) (devId >> 8);
            mData[7] = (byte) (devId & 0xFF);

            mData[8] = (byte) ((msgLen & 0xFF000000) >> 24);
            mData[9] = (byte) ((msgLen & 0x00FF0000) >> 16);
            mData[10] = (byte) ((msgLen & 0x0000FF00) >> 8);
            mData[11] = (byte) (msgLen & 0x000000FF);

            mData[12] = 0x00;
            mData[13] = 0x00;

            mData[14] = 0x00;
            mData[15] = 0x00;

            initParam(mData);
        }

        private void initParam(byte[] data) {
            mVersion = (short) ((data[0] << 8) | (data[1] & 0x0FF));
            mOptType = (short) ((data[2] << 8) | (data[3] & 0x0FF));
            mDeviceType = (short) ((data[4] << 8) | (data[5] & 0x0FF));
            mDeviceId = (short) ((data[6] << 8) | (data[7] & 0x0FF));
            mPayloadLength = ((data[8] << 24) | ((data[9] & 0x0FF) << 16)
                    | ((data[10] & 0x0FF) << 8) | (data[11] & 0x0FF)) - MSG_HEADER_LEN;
            mNextProtocol = (short) ((data[12] << 8) | (data[13] & 0x0FF));
            mHopLimit = (short) ((data[14] << 8) | (data[15] & 0x0FF));
        }

        public byte[] getData() {
            return mData;
        }

        @Override
        public String toString() {
            return "MsgHeader{" +
                    "mVersion=" + mVersion +
                    ", mOptType=" + mOptType +
                    ", mDeviceType=" + mDeviceType +
                    ", mDeviceId=" + mDeviceId +
                    ", mPayloadLength=" + mPayloadLength +
                    ", mNextProtocol=" + mNextProtocol +
                    ", mHopLimit=" + mHopLimit +
                    "}";
        }
    }
}


